﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace Ue.Eagle.Runtime
{
    public interface IAssemblyLoader
    {
        Assembly Load(string assemblyName);
    }

    public class AssemblyLoader : IAssemblyLoader
    {
        private readonly IEnumerable<IAssemblyNameResolver> assemblyNameResolvers;
        private readonly ConcurrentDictionary<string, Assembly> loadedAssemblies;

        public AssemblyLoader(IEnumerable<IAssemblyNameResolver> assemblyNameResolvers)
        {
            this.loadedAssemblies = new ConcurrentDictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
            this.assemblyNameResolvers = assemblyNameResolvers;
        }

        public Assembly Load(string assemblyName)
        {

            try
            {
                return this.loadedAssemblies.GetOrAdd(Ue.Utility.AssemblyHelper.ExtractAssemblyShortName(assemblyName), shortName => this.LoadWorker(shortName, assemblyName));
            }
            catch (Exception)
            {
                return null;
            }
        }

        private Assembly LoadWorker(string shortName, string fullName)
        {
            Assembly result;

            //根据 “全名” 加载Assembly
            if (fullName != shortName)
            {
                result = TryAssemblyLoad(fullName);
                if (result != null)
                    return result;
            }
            
            // 根据“短名”加载Assembly
            result = TryAssemblyLoad(shortName);
            if (result != null)
                return result;

            // 重新获取应用程序全名，并再次加载
            var resolvedName = this.assemblyNameResolvers.Select(r => r.Resolve(shortName)).FirstOrDefault(f => f != null);
            if (resolvedName != null)
            {
                return Assembly.Load(resolvedName);
            }

            // 再次根据全名加载，如何出错则会抛出异常
            return Assembly.Load(fullName);
        }

        private static Assembly TryAssemblyLoad(string name)
        {
            try
            {
                return Assembly.Load(name);
                
            }
            catch
            {
                return null;
            }
        }
    }
}
